(function () {
    CodeMirror.simpleHint = function (editor, getHints, givenOptions) {
        // Determine effective options based on given values and defaults.
        var options = {}, defaults = CodeMirror.simpleHint.defaults;
        for (var opt in defaults)
            if (defaults.hasOwnProperty(opt))
                options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt];

        function collectHints(previousToken) {
            // We want a single cursor position.
            if (editor.somethingSelected()) return;

            var tempToken = editor.getTokenAt(editor.getCursor());

            // Don't show completions if token has changed and the option is set.
            if (options.closeOnTokenChange && previousToken != null &&
                (tempToken.start != previousToken.start || tempToken.className != previousToken.className)) {
                return;
            }

            var result = getHints(editor);
            if (!result || !result.list.length) return;
            var completions = result.list;

            function insert(str) {
                editor.replaceRange(str, result.from, result.to);
            }

            // When there is only one completion, use it directly.
            if (options.completeSingle && completions.length == 1) {
                insert(completions[0]);
                return true;
            }

            // Build the select widget
            var complete = document.createElement("div");
            complete.className = "CodeMirror-completions";
            var sel = complete.appendChild(document.createElement("select"));
            // Opera doesn't move the selection when pressing up/down in a
            // multi-select, but it does properly support the size property on
            // single-selects, so no multi-select is necessary.
            if (!window.opera) sel.multiple = true;
            for (var i = 0; i < completions.length; ++i) {
                var opt = sel.appendChild(document.createElement("option"));
                opt.appendChild(document.createTextNode(completions[i]));
            }
            sel.firstChild.selected = true;
            sel.size = Math.min(10, completions.length);
            var pos = options.alignWithWord ? editor.charCoords(result.from) : editor.cursorCoords();
            complete.style.left = pos.x + "px";
            complete.style.top = pos.yBot + "px";
            document.body.appendChild(complete);
            // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
            var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);
            if (winW - pos.x < sel.clientWidth)
                complete.style.left = (pos.x - sel.clientWidth) + "px";
            // Hack to hide the scrollbar.
            if (completions.length <= 10)
                complete.style.width = (sel.clientWidth - 1) + "px";

            var done = false;

            function close() {
                if (done) return;
                done = true;
                complete.parentNode.removeChild(complete);
            }

            function pick() {
                insert(completions[sel.selectedIndex]);
                close();
                setTimeout(function () {
                    editor.focus();
                }, 50);
            }

            CodeMirror.connect(sel, "blur", close);
            CodeMirror.connect(sel, "keydown", function (event) {
                var code = event.keyCode;
                // Enter
                if (code == 13) {
                    CodeMirror.e_stop(event);
                    pick();
                }
                // Escape
                else if (code == 27) {
                    CodeMirror.e_stop(event);
                    close();
                    editor.focus();
                }
                else if (code != 38 && code != 40 && code != 33 && code != 34 && !CodeMirror.isModifierKey(event)) {
                    close();
                    editor.focus();
                    // Pass the event to the CodeMirror instance so that it can handle things like backspace properly.
                    editor.triggerOnKeyDown(event);
                    // Don't show completions if the code is backspace and the option is set.
                    if (!options.closeOnBackspace || code != 8) {
                        setTimeout(function () {
                            collectHints(tempToken);
                        }, 50);
                    }
                }
            });
            CodeMirror.connect(sel, "dblclick", pick);

            sel.focus();
            // Opera sometimes ignores focusing a freshly created node
            if (window.opera) setTimeout(function () {
                if (!done) sel.focus();
            }, 100);
            return true;
        }

        return collectHints();
    };
    CodeMirror.simpleHint.defaults = {
        closeOnBackspace: true,
        closeOnTokenChange: false,
        completeSingle: true,
        alignWithWord: true
    };
})();
